package com.scpii.api.common.auth.filter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.Resource;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import com.scpii.api.common.auth.ClientAuthenticationDetails;
import com.scpii.api.common.auth.ClientAuthenticationDetailsSource;
import com.scpii.api.common.auth.token.DefaultRequestToken;
import com.scpii.api.common.auth.token.HeaderRquestToken;
import com.scpii.api.common.auth.token.RequestToken;
import com.scpii.api.common.exception.ClientException;

@Component
public class ResourceAuthenticationProcessingFilter implements Filter,
		InitializingBean {

	private final static Log logger = LogFactory
			.getLog(ResourceAuthenticationProcessingFilter.class);

	@Resource(name = "clientAuthenticationEntryPoint")
	private AuthenticationEntryPoint authenticationEntryPoint;

	@Resource(name = "resourceAuthenticationManager")
	private AuthenticationManager authenticationManager;

	private List<RequestToken> requestTokens;

	private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new ClientAuthenticationDetailsSource();

	public void setAuthenticationEntryPoint(
			AuthenticationEntryPoint authenticationEntryPoint) {
		this.authenticationEntryPoint = authenticationEntryPoint;
	}

	public void setAuthenticationManager(
			AuthenticationManager authenticationManager) {
		this.authenticationManager = authenticationManager;
	}

	public void setAuthenticationDetailsSource(
			AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
		Assert.notNull(authenticationDetailsSource,
				"AuthenticationDetailsSource required");
		this.authenticationDetailsSource = authenticationDetailsSource;
	}

	public void afterPropertiesSet() {
		Assert.state(authenticationManager != null,
				"AuthenticationManager is required");

		requestTokens = new ArrayList<RequestToken>();
		requestTokens.add(new DefaultRequestToken());
		requestTokens.add(new HeaderRquestToken());
	}

	public void doFilter(ServletRequest req, ServletResponse res,
			FilterChain chain) throws IOException, ServletException {

		final boolean debug = logger.isDebugEnabled();
		final HttpServletRequest request = (HttpServletRequest) req;
		final HttpServletResponse response = (HttpServletResponse) res;
		try {

			String tokenValue = parseToken(request);
			if (tokenValue == null) {
				if (debug) {
					logger.debug("No token in request, will continue chain.");
				}
			} else {
				PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(
						tokenValue, "");
				request.setAttribute(
						ClientAuthenticationDetails.ACCESS_TOKEN_VALUE,
						tokenValue);
				authentication.setDetails(authenticationDetailsSource
						.buildDetails(request));
				Authentication authResult = authenticationManager
						.authenticate(authentication);

				if (debug) {
					logger.debug("Authentication success: " + authResult);
				}

				SecurityContextHolder.getContext()
						.setAuthentication(authResult);

			}
		} catch (ClientException failed) {
			SecurityContextHolder.clearContext();

			if (debug) {
				logger.debug("Authentication request failed: " + failed);
			}

			authenticationEntryPoint.commence(request, response,
					new InsufficientAuthenticationException(
							failed.getMessage(), failed));

			return;
		}
		try {
			chain.doFilter(request, response);
		} catch (Exception failed) {
			if (debug) {
				logger.debug("Internal Server Error	", failed);
			}
			authenticationEntryPoint.commence(request, response,
					new InsufficientAuthenticationException(
							failed.getMessage(), failed));
		}
	}

	protected String parseToken(HttpServletRequest request) {
		String token = null;
		logger.debug("parse token in  request parameters. begin ");
		for (RequestToken requestToken : requestTokens) {
			token = requestToken.getRequestToken(request);
			if (token != null) {
				break;
			}
		}
		if (token == null) {
			logger.debug("Token not found in request parameters.  Not an Auth request.");
		}
		logger.debug("parse token in  request parameters. end ");
		return token;
	}

	public void init(FilterConfig filterConfig) throws ServletException {
	}

	public void destroy() {
	}

}
